package com.zoe.onelink.feign.loadbalancer;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.net.Ipv4Util;
import com.alibaba.cloud.nacos.ribbon.NacosServer;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ZoneAwareLoadBalancer;
import com.zoe.onelink.util.IpUtil;
import com.zoe.onelink.util.OnelinkContextHolder;
import com.zoe.onelink.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.context.request.RequestContextHolder;

import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;

/**
 * <p>标题: </p>
 * <p>描述: </p>
 * <p>版权: Copyright (c) 2022</p>
 * <p>公司: 智业软件股份有限公司</p>
 *
 * @version: 1.0
 * @author: ruansheng
 * @date: 2022-07-20
 */
@Slf4j
public class OnelinkDevLoadBalanceRule extends AbstractLoadBalancerRule {

    private static Collection<String> LOCAL_IPS;

    @Autowired
    public HttpServletRequest request;

    @Value("#{'${onelink.loadbalance.ips-weight:}'.split(',')}")
    private List<String> priorityIps;

    static {
        try {
            LOCAL_IPS = IpUtil.localIpv4s();
            LOCAL_IPS.remove("127.0.0.1");
            LOCAL_IPS.remove("localhost");
        } catch (Exception e) {
            LOCAL_IPS = Collections.emptySet();
            log.warn("获取本地IP地址列表失败");
        }
    }

    @Override
    public Server choose(Object key) {
        ILoadBalancer lb = this.getLoadBalancer();
        String serviceName = lb instanceof ZoneAwareLoadBalancer ? ((ZoneAwareLoadBalancer) lb).getName() : "unknown";
        List<Server> allServers = lb.getAllServers();
        if (allServers == null || allServers.isEmpty()) {
            log.info("[请求IP负载策略] 调用 [{}] 没有可用的服务实例列表", serviceName);
            return null;
        }
        String ips = allServers.stream()
                .map(server -> server.getHost() + ":" + server.getPort())
                .reduce((s1, s2) -> s1 + " , " + s2)
                .orElse("");
        log.info("[请求IP负载策略] 调用 [{}] 可用的服务实例列表: {}", serviceName, ips);
        int serverCount = allServers.size();
        Server server = this.chooseByIp(allServers, serviceName);
        if (server != null) {
            return server;
        }
        int idx = ThreadLocalRandom.current().nextInt(serverCount);
        return allServers.get(idx);
    }

    /**
     * 根据IP优先级选择实例
     */
    private Server chooseByIp(List<Server> instances, String serviceId) {
        if (instances == null) {
            return null;
        }
        String clientIp = null;
        try {
            clientIp = this.getClientIp(this.request);
        } catch (Throwable e) {
            log.warn("[请求IP负载策略] 获取客户端IP失败: {}", e.getMessage());
        }
        if (StrUtil.isBlank(clientIp)) {
            log.warn("[请求IP负载策略] 调用 [{}] 客户端IP地址为空,使用默认负载策略", serviceId);
        }
        // 优先选择与客户端一致的IP
        String finalClientIp = clientIp;
        Server sameIpInstance = instances.stream()
                .filter(serviceInstance -> serviceInstance.getHost().equals(finalClientIp)).findFirst()
                .orElse(null);

        if (sameIpInstance != null) {
            log.info("[请求IP负载策略] 调用 [{}] 注册中心服务列表中包含与客户端一致的IP [{}] , 已将请求转发至该IP", serviceId, clientIp);
            return sameIpInstance;
        }

        // 线上服务优先转发
        List<Server> linuxServers = instances.stream()
                .filter(server -> server instanceof NacosServer)
                .filter(server -> {
                    Map<String, String> metadata = ((NacosServer) server).getMetadata();
                    if (metadata == null) {
                        return false;
                    }
                    return "linux".equalsIgnoreCase(metadata.get("os.name"));
                }).collect(Collectors.toList());

        // 优先转发到线上服务
        if (!linuxServers.isEmpty()) {
            int randomIdx = ThreadLocalRandom.current().nextInt(0, linuxServers.size());
            Server server = linuxServers.get(randomIdx);
            log.info("[请求IP负载策略] 调用 [{}] 找到{}个线上服务,优先转发到线上服务 [{}:{}]", serviceId, linuxServers.size(), server.getHost(), server.getPort());
            return server;
        }
        if (log.isDebugEnabled()) {
            List<String> serviceIps = instances.stream().map(Server::getHost).collect(Collectors.toList());
            log.info("[请求IP负载策略] 调用 [{}] 开始根据优先IP列表 {} 匹配注册中心服务实例列表 {}", serviceId, this.priorityIps, serviceIps);
        }

        // 根据优先级IP配置选择实例
        Server priorityInstance = this.chooseInstanceByIpMask(instances, serviceId, this.priorityIps);
        if (priorityInstance != null) {
            return priorityInstance;
        }

        // 本机IP地址列表
        if (LOCAL_IPS.isEmpty()) {
            log.warn("[请求IP负载策略] 调用 [{}] 获取本机IP列表失败", serviceId);
            return null;
        }

        if (log.isDebugEnabled()) {
            List<String> serviceIps = instances.stream().map(Server::getHost).collect(Collectors.toList());
            log.info("[请求IP负载策略] 调用 [{}] 开始根据本地IP列表 {} 匹配注册中心服务实例列表 {}", serviceId, LOCAL_IPS, serviceIps);
        }
        return chooseInstanceByIpMask(instances, serviceId, LOCAL_IPS);
    }

    private Server chooseInstanceByIpMask(List<Server> instances, String serviceId, Collection<String> calcIps) {
        if (CollUtil.isEmpty(calcIps)) {
            return null;
        }
        // 根据IP列表进行匹配
        for (int i = 4; i > 0; i--) {
            int maskBit = i * 8;
            for (String calcIp : calcIps) {
                if (StrUtil.isBlank(calcIp)) {
                    continue;
                }
                String calcIpBeginStr = Ipv4Util.getBeginIpStr(calcIp.replace(" ", ""), maskBit);
                Server ipRangeInstance = instances.stream()
                        .filter(serviceInstance -> {
                            String serviceIp = serviceInstance.getHost();
                            String serviceBeginIpStr = Ipv4Util.getBeginIpStr(serviceIp, maskBit);
                            return calcIpBeginStr.equals(serviceBeginIpStr);
                        }).findFirst()
                        .orElse(null);
                if (ipRangeInstance == null) {
                    continue;
                }
                log.info("[请求IP负载策略] 调用 [{}] 尝试通过IP段 [{}] 匹配成功,转发当前请求到实例IP: {}", serviceId, calcIpBeginStr, ipRangeInstance.getHost());
                return ipRangeInstance;
            }
        }
        return null;
    }

    /**
     * 获取客户端IP
     */
    private String getClientIp(HttpServletRequest request) {
        String clientIp = OnelinkContextHolder.getString("clientIp");
        if (StrUtil.isNotBlank(clientIp)) {
            return clientIp;
        }
        if (RequestContextHolder.getRequestAttributes() == null) {
            return null;
        }
        String ipAddress = IpUtil.getIpAddr(request);
        if ("127.0.0.1".equals(ipAddress) || "localhost".equals(ipAddress)) {
            String originIp = IpUtil.parseIpAndPort(request.getHeader("Origin"));
            if (!org.springframework.util.StringUtils.isEmpty(originIp)) {
                String[] ipAndPort = originIp.split(":");
                ipAddress = ipAndPort[0];
            }
        }
        return ipAddress;
    }

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {

    }
}
